﻿using System;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;

using EnvDTE;

using EnvDTE80;

using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;

using Constants = EnvDTE.Constants;

namespace VsExt.SolutionCodeAnalysis
{
    [PackageRegistration(UseManagedResourcesOnly = true)]
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [Guid(GuidList.guidPkgString)]
    [ProvideAutoLoad(UIContextGuids80.SolutionExists)]
    public sealed class VsExt_SolutionCodeAnalysisPackage : Package, IVsSolutionEvents
    {
        private DTE2 _dte;
        private string _outputWindowName;
        private IVsSolution _solution;
        private uint _solutionEventsCookie;

        /// <summary>
        ///   Default constructor of the package.
        /// </summary>
        /// <remarks>
        /// As long as the class is marked with the [ProvideAutoLoad(UIContextGuids80.SolutionExists)] attribute
        /// initialize will only be called once a solution is opened.
        /// </remarks>
        public VsExt_SolutionCodeAnalysisPackage()
        {
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", ToString()));
        }

        # region IVsSolutionEvents

        public int OnAfterCloseSolution(object pUnkReserved)
        {
            // once the solution is closed we need to hide the menu commands. 
            // they should be visible on when a solution is opened.  
            showMenuCommands(false);

            return VSConstants.S_OK;
        }

        public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy)
        {
            return VSConstants.S_OK;
        }

        public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded)
        {
            return VSConstants.S_OK;
        }

        public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution)
        {
            // once a solution is opened we need to make the menu commands visible.
            showMenuCommands(true);

            return VSConstants.S_OK;
        }

        public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved)
        {
            return VSConstants.S_OK;
        }

        public int OnBeforeCloseSolution(object pUnkReserved)
        {
            return VSConstants.S_OK;
        }

        public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy)
        {
            return VSConstants.S_OK;
        }

        public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel)
        {
            return VSConstants.S_OK;
        }

        public int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel)
        {
            return VSConstants.S_OK;
        }

        public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel)
        {
            return VSConstants.S_OK;
        }

        #endregion

        /// <summary>
        ///   Initialization of the package; this method is called right after the package is sited, so this is the place where you can put all the initialization code that rely on services provided by VisualStudio.
        /// </summary>
        protected override void Initialize()
        {
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", ToString()));

            base.Initialize();

            initializeDte();

            initializeMenuCommands();

            initializeOutputWindows();

            initializeSolutionEvents();
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary>
        ///  Gets an instance of the IVsSolution service and subscribes to it's events.
        /// </summary>
        ///-------------------------------------------------------------------------------------------------
        private void initializeSolutionEvents()
        {
            _solution = GetService(typeof(SVsSolution)) as IVsSolution;

            if (_solution != null)
            {
                _solution.AdviseSolutionEvents(this, out _solutionEventsCookie);
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary>
        ///  Gets an instance of the DTE2 service.
        /// </summary>
        ///-------------------------------------------------------------------------------------------------
        private void initializeDte()
        {
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "VsExt - Initializing the DTE service.."));

            _dte = (DTE2)GetService(typeof(DTE));
        }

        ///-------------------------------------------------------------------------------------------------
        ///<summary>
        ///  Creates the menu commands.
        ///</summary>
        ///-------------------------------------------------------------------------------------------------
        private void initializeMenuCommands()
        {
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "VsExt - Entering Initialize() of menu commands."));

            // Add our command handlers for menu (commands must exist in the .vsct file)
            OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if (null == mcs)
            {
                return;
            }

            // Create the command for the menu item.
            CommandID menuCommandEnable = createEnableCommandID();
            MenuCommand menuItemEnable = new MenuCommand(enableCodeAnalysis, menuCommandEnable) { Visible = true };
            mcs.AddCommand(menuItemEnable);

            CommandID menuCommandDisable = createDisableCommandID();
            MenuCommand menuItemDisable = new MenuCommand(disableCodeAnalysis, menuCommandDisable) { Visible = true };
            mcs.AddCommand(menuItemDisable);
        }

        ///-------------------------------------------------------------------------------------------------
        ///<summary>
        ///  Creates the output window pane so the extension will have a special place to write it's output.
        ///</summary>
        ///-------------------------------------------------------------------------------------------------
        private void initializeOutputWindows()
        {
            OutputWindow windowPane = _dte.ToolWindows.OutputWindow;

            // Add a new pane to the Output window.
            _outputWindowName = "Enable Code Analysis";
            windowPane.OutputWindowPanes.Add(_outputWindowName);
        }

        ///-------------------------------------------------------------------------------------------------
        ///<summary>
        ///  Enables Code Analysis for all projects in the solution.
        ///</summary>
        ///-------------------------------------------------------------------------------------------------
        private void enableCodeAnalysis(object sender, EventArgs e)
        {
            Trace.WriteLine("Beginning Enabling of Code Analysis.");

            CodeAnalysis analysis = new CodeAnalysis(_dte);
            analysis.WriteToLog += writeToLog;

            analysis.Enable();
        }

        ///-------------------------------------------------------------------------------------------------
        ///<summary>
        ///  Writes the data to a user accessibly log. In this case an output window.
        ///</summary>
        ///-------------------------------------------------------------------------------------------------
        private void writeToLog(object sender, TraceEventArgs e)
        {
            OutputWindowPane wp = getOutputWindowPane(true);

            wp.OutputString(string.Format("{0} - {1}\n", e.Info, DateTime.Now));
        }

        ///-------------------------------------------------------------------------------------------------
        ///<summary>
        ///  Disables code analysis for each project in the solution.
        ///</summary>
        ///-------------------------------------------------------------------------------------------------
        private void disableCodeAnalysis(object sender, EventArgs e)
        {
            Trace.WriteLine("Beginning Disabling of Code Analysis.");

            CodeAnalysis analysis = new CodeAnalysis(_dte);
            analysis.WriteToLog += writeToLog;

            analysis.Disable();
        }

        ///-------------------------------------------------------------------------------------------------
        ///<summary>
        ///  Returns a reference to the OutputWindows. If the window pane does not exist then it will be created.
        ///</summary>
        ///-------------------------------------------------------------------------------------------------
        private OutputWindowPane getOutputWindowPane(bool show = false)
        {
            Window win = _dte.Windows.Item(Constants.vsWindowKindOutput);

            if (show)
            {
                win.Visible = true;
            }

            OutputWindow ow = (OutputWindow)win.Object;

            OutputWindowPane owpane;

            try
            {
                owpane = ow.OutputWindowPanes.Item(_outputWindowName);
            }
            catch (Exception)
            {
                owpane = ow.OutputWindowPanes.Add(_outputWindowName);
            }

            owpane.Activate();
            return owpane;
        }

        ///-------------------------------------------------------------------------------------------------
        ///<summary>
        ///  Makes the 'Enable' and 'Disable' commands visible in the menu.
        ///</summary>
        /// <remarks>
        /// This isn't really necessary if we are embedding the commands in the build menu since it's
        /// only visible when a solution is opened.
        /// </remarks>
        ///-------------------------------------------------------------------------------------------------
        private void showMenuCommands(bool visibility)
        {
            OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;

            if (mcs == null) return;

            MenuCommand disableCommand = mcs.FindCommand(createDisableCommandID());
            if (disableCommand != null) disableCommand.Visible = visibility;

            MenuCommand enableCommand = mcs.FindCommand(createEnableCommandID());
            if (enableCommand != null) enableCommand.Visible = visibility;
        }

        ///-------------------------------------------------------------------------------------------------
        ///<summary>
        ///  Creates the CommandID used to identify the 'Disable' command.
        ///</summary>
        ///-------------------------------------------------------------------------------------------------
        private static CommandID createDisableCommandID()
        {
            return new CommandID(GuidList.guidMenuCmdSet, (int)PkgCmdIDList.cmdidDisableAnalysis);
        }

        ///-------------------------------------------------------------------------------------------------
        ///<summary>
        ///  Creates the CommandID used to identity the 'Enable' command.
        ///</summary>
        ///-------------------------------------------------------------------------------------------------
        private static CommandID createEnableCommandID()
        {
            return new CommandID(GuidList.guidMenuCmdSet, (int)PkgCmdIDList.cmdidEnableAnalysis);
        }
    }
}